#include <linux/version.h>/* LINUX_VERSION_CODE KERNEL_VERSION()*/
#include <linux/init.h>   /* module_init module_exit */
#include <linux/kernel.h>
#include <linux/module.h> /* MODULE_LICENSE */

#include <linux/platform_device.h>
#include <linux/mod_devicetable.h> /* struct platform_device_id */

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#include <linux/delay.h> /* ssleep */

#include <linux/mutex.h>

#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/uaccess.h> /* access_ok */

#include <linux/of.h>
#include <linux/of_address.h>

#include "./timestamp_autogenerated.h"
#include "./version_autogenerated.h"
#include "./sdrv_common.h"

// #undef PRT
// #undef PRTE
#ifndef PRT
#define PRT printk
#endif
#ifndef PRTE
#define PRTE printk
#endif

#define CHRDEV_NAME "sdev"    /* 设备名,cat /proc/devices 查看与设备号的对应关系 */
#define CLASS_NAME  "sclass"  /* 类名，在 /sys/class 中显示的名称 */
#define DEVICE_NAME "sdevchr" /* 设备节点名，在 /sys/class/class_name/ 中显示的名称以及 /dev/ 下显示的节点名 */
#define BUFSIZE     32        /* 设置最大偏移量为32 */
#define MUTEX_FLAG  0         /* 用于有无互斥锁的现象对比，这里屏蔽互斥锁 */
// ioctl 支持的命令定义
#define CMD_TEST0 _IO('S', 0)
#define CMD_TEST1 _IOW('S', 1, int)
#define CMD_TEST2 _IOR('S', 2, int)
#define CMD_TEST3 _IOW('S', 3, int)

struct __CMD_TEST{
	int a;
	int b;
	int c;
};

#define LEDOFF                      ('0')     /* 关灯 */
#define LEDON                       ('1')     /* 开灯 */
/* 映射后的寄存器虚拟地址指针 */
typedef struct __REG_MAP_REGISTER_{
    void __iomem *ccm_ccgr1;
    void __iomem *sw_mux_gpiox_iox;
    void __iomem *sw_pad_gpiox_iox;
    void __iomem *gpiox_dr;
    void __iomem *gpiox_gdir;
}reg_map_t;

typedef struct __CHAR_DEVICE
{
    char dev_name[32];     // 设备名称， /dev/dev-name
    dev_t dev_num;         // 定义dev_t类型（32位大小）的变量dev_num,用来存放设备号
    struct cdev s_cdev;    // 定义cdev结构体类型的变量scdev
    struct class *class;   // 定于struct class *类型结构体变量 class，表示要创建的类
    struct device *device; // 设备
    char buf[BUFSIZE];     // 设置数据存储数组mem
    struct mutex mutex_lock;  // 互斥锁
    uint32_t condition;    // 等待条件， 为假时，调用进程才可以进入休眠。 如果 condition 为真， 则不会休眠
    wait_queue_head_t r_wait;// 读等待队列头
    struct fasync_struct *fasync_queue;// 异步相关结构体

    reg_map_t led_reg;  // LED灯GPIO映射后的地址信息
    struct device_node *nd; // 设备节点
} _CHAR_DEVICE;

_CHAR_DEVICE g_chrdev = {0};  //定义一个device_test结构体变量

/**
 * @brief  imx6ull_led_init()
 * @note   LED灯初始化函数
 * @param [in]
 * @param [out]
 * @retval 
 */
static int imx6ull_led_init(_CHAR_DEVICE *p_chrdev)
{
    uint32_t val = 0;	

	/* 初始化LED */
	/* 2、使能GPIO1时钟 */
	val = readl(p_chrdev->led_reg.ccm_ccgr1);
	val &= ~(3 << 26);	/* 清楚以前的设置 */
	val |= (3 << 26);	/* 设置新值 */
	writel(val, p_chrdev->led_reg.ccm_ccgr1);

	/* 3、设置GPIO1_IO03的复用功能，将其复用为GPIO1_IO03，最后设置IO属性。*/
	writel(5, p_chrdev->led_reg.sw_mux_gpiox_iox);
	
	/**
     * 寄存器sw_pad_gpiox_iox设置IO属性
	 * bit 16:0 HYS关闭
	 * bit [15:14]: 00 默认下拉
     * bit [13]: 0 kepper功能
     * bit [12]: 1 pull/keeper使能
     * bit [11]: 0 关闭开路输出
     * bit [7:6]: 10 速度100Mhz
     * bit [5:3]: 110 R0/6驱动能力
     * bit [0]: 0 低转换率
	 */
	writel(0x10B0, p_chrdev->led_reg.sw_pad_gpiox_iox);

	/* 4、设置GPIO1_IO03为输出功能 */
	val = readl(p_chrdev->led_reg.gpiox_gdir);
	val &= ~(1 << 3);	/* 清除以前的设置 */
	val |= (1 << 3);	/* 设置为输出 */
	writel(val, p_chrdev->led_reg.gpiox_gdir);

	/* 5、默认关闭LED */
	val = readl(p_chrdev->led_reg.gpiox_dr);
	val |= (1 << 3);	
	writel(val, p_chrdev->led_reg.gpiox_dr);

    return 0;
}

/**
 * @brief  led_switch()
 * @note   LED打开/关闭
 * @param [in] sta LEDON(0) 打开LED，LEDOFF(1) 关闭LED
 * @param [out]
 * @retval 
 */
static int imx6ull_led_ctl(_CHAR_DEVICE *p_chrdev, uint8_t sta)
{
	uint32_t val = 0;

	if(sta == LEDON) 
	{
		val = readl(p_chrdev->led_reg.gpiox_dr);
		val &= ~(1 << 3);	
		writel(val, p_chrdev->led_reg.gpiox_dr);
	}
	else if(sta == LEDOFF) 
	{
		val = readl(p_chrdev->led_reg.gpiox_dr);
		val |= (1 << 3);	
		writel(val, p_chrdev->led_reg.gpiox_dr);
	}	
    return 0;
}

/**
 * @brief  imx6ull_led_uinit()
 * @note   led 去初始化
 * @param [in]
 * @param [out]
 * @retval 
 */
static void imx6ull_led_uinit(_CHAR_DEVICE *p_chrdev)
{
    if(p_chrdev == NULL)
    {
        PRTE("p_chrdev is NULL!\n");
        return;
    }
    /* 取消映射 */
	iounmap(p_chrdev->led_reg.ccm_ccgr1);
	iounmap(p_chrdev->led_reg.sw_mux_gpiox_iox);
	iounmap(p_chrdev->led_reg.sw_pad_gpiox_iox);
	iounmap(p_chrdev->led_reg.gpiox_dr);
	iounmap(p_chrdev->led_reg.gpiox_gdir);
}

/**
 * @brief  imx6ull_led_register_map()
 * @note   led 引脚映射
 * @param [in]
 * @param [out]
 * @retval 
 */
static int imx6ull_led_register_map(_CHAR_DEVICE *p_chrdev)
{
    int ret = 0;
    uint32_t regdata[16] = {0};
	const char *str = NULL;
	struct property *proper = NULL;

    if(p_chrdev == NULL)
    {
        PRTE("p_chrdev is NULL!\n");
        return -1;
    }

	/* 获取设备树中的属性数据 */
	/* 1、获取设备节点：sdev_led */
	//p_chrdev->nd = of_find_node_by_path("/alpha/sdev_led");

    if (p_chrdev->nd == NULL)
    {
        PRTE("sdev_led node nost find!\r\n");
        return -EINVAL;
    }

    /* 2、获取compatible属性内容 */
    proper = of_find_property(p_chrdev->nd, "compatible", NULL);
    if (proper == NULL)
    {
        PRTE("compatible property find failed\r\n");
    }
    PRT("compatible = %s\r\n", (char *)proper->value);

    /* 3、获取status属性内容 */
    ret = of_property_read_string(p_chrdev->nd, "status", &str);
    if (ret < 0)
    {
        PRTE("status read failed!\r\n");
    }

    /* 4、获取reg属性内容 */
    ret = of_property_read_u32_array(p_chrdev->nd, "reg", regdata, 10);
    if (ret < 0)
    {
        printk("reg property read failed!\r\n");
        return -EINVAL; 
    }
	/* 初始化LED */
    #if 1
	/* 1、寄存器地址映射 */
	p_chrdev->led_reg.ccm_ccgr1 = ioremap(regdata[0], regdata[1]);
	p_chrdev->led_reg.sw_mux_gpiox_iox = ioremap(regdata[2], regdata[3]);
  	p_chrdev->led_reg.sw_pad_gpiox_iox = ioremap(regdata[4], regdata[5]);
	p_chrdev->led_reg.gpiox_dr = ioremap(regdata[6], regdata[7]);
	p_chrdev->led_reg.gpiox_gdir = ioremap(regdata[8], regdata[9]);
    #else
	p_chrdev->led_reg.ccm_ccgr1 = of_iomap(p_chrdev->nd, 0);
	p_chrdev->led_reg.sw_mux_gpiox_iox = of_iomap(p_chrdev->nd, 1);
  	p_chrdev->led_reg.sw_pad_gpiox_iox = of_iomap(p_chrdev->nd, 2);
	p_chrdev->led_reg.gpiox_dr = of_iomap(p_chrdev->nd, 3);
	p_chrdev->led_reg.gpiox_gdir = of_iomap(p_chrdev->nd, 4);
    #endif

    PRT("register ioremap: 0x%px 0x%px 0x%px 0x%px 0x%px\n",
            p_chrdev->led_reg.ccm_ccgr1,
            p_chrdev->led_reg.sw_mux_gpiox_iox,
            p_chrdev->led_reg.sw_pad_gpiox_iox,
            p_chrdev->led_reg.gpiox_dr,
            p_chrdev->led_reg.gpiox_gdir);

    return 0;
}

static int scdev_ops_open(struct inode *pInode, struct file *pFile)
{
    //PRT("This is scdev_ops_open!dev name=%s\n", g_chrdev.dev_name);
    pFile->private_data = &g_chrdev;  // 设置私有数据
    #if MUTEX_FLAG == 1
    #if 1
	/* 获取互斥锁,可以被信号打断 */
	if (mutex_lock_interruptible(&g_chrdev.mutex_lock)) {
		return -ERESTARTSYS;
	}
    #else
    mutex_lock(&g_chrdev.mutex_lock);	/* 不能被信号打断 */
    #endif
    #endif

    return 0;
}

static ssize_t scdev_ops_read(struct file *pFile, char __user *buf, size_t size, loff_t *off)
{
    
    loff_t offset = *off; // 将读取数据的偏移量赋值给loff_t类型变量p
    size_t count = size;
    _CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)pFile->private_data; //在write函数中读取private_data

    if (offset > BUFSIZE)
    {
        return 0;
    }

    if (count > BUFSIZE - offset)
    {
        count = BUFSIZE - offset;
    }

    if (pFile->f_flags & O_NONBLOCK) // 实现非阻塞
    {
        // 非阻塞的时候，判断一下是否有数据写入，当有数据写入的时候 condition = 1,后面也可以直接执行不会进如休眠
        // 若是没有数据写入，这里就直接返回一个错误。
        if (p_chrdev->condition != 1)
            return -EAGAIN;
    }
    // 若是非阻塞的话，可以执行到这里，那么 condition = 1，下面这行等待事件唤醒的，条件直接满足，调用进程不会休眠
    wait_event_interruptible(p_chrdev->r_wait, p_chrdev->condition); // 可中断的阻塞等待，使进程进入休眠态,condition为真时则不会休眠
    /* 当进程被唤醒的时候，有数据会写入，p_chrdev->condition = 1,保证下一次读依然为写了数据才能正常读，这里要清0一下*/
    p_chrdev->condition = 0;
    if (copy_to_user(buf, p_chrdev->buf + offset, count))
    { 
        // 将mem中的值写入buf，并传递到用户空间
        PRT("copy_to_user error!\n");
        return -1;
    }
    #if 0
    int i = 0;
    for (i = 0; i < BUFSIZE; i++)
    {
        PRT("buf[%d] %c\n", i, p_chrdev->buf[i]); // 将mem中的值打印出来
    }
    
    PRT("read offset is %llu, count is %d\n", offset, count);
    #endif
    *off = *off + count; // 更新偏移值

    return count;
}

static ssize_t scdev_ops_write(struct file *pFile, const char __user *buf, size_t size, loff_t *off)
{
    unsigned char ledstat;

    loff_t offset = *off; // 将读取数据的偏移量赋值给loff_t类型变量p
    size_t count = size;
    _CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)pFile->private_data; //在write函数中读取private_data

    if (offset > BUFSIZE)
    {
        return 0;
    }

    if (count > BUFSIZE - offset)
    {
        count = BUFSIZE - offset;
    }

    if (copy_from_user(p_chrdev->buf + offset, buf, count))
    { 
        // 将buf中的值，从用户空间传递到内核空间
        PRT("copy_to_user error \n");
        return -1;
    }
    //PRT("copy_from_user buf is %s\n", p_chrdev->buf + offset);
    p_chrdev->condition = 1;// 将条件置1，表示有数据写入，需要唤醒进程了
    // 使用wake_up_interruptible唤醒等待队列中的休眠进程
    wake_up_interruptible(&p_chrdev->r_wait);

    // 使用信号驱动IO时，通知应用程序, 然后此时应用程序的 SIGIO 信号处理函数就会被执行。
    kill_fasync(&p_chrdev->fasync_queue, SIGIO, POLLOUT);

    // led控制
    ledstat = *(p_chrdev->buf + offset);/* 获取状态值 */
	if(ledstat == LEDON) 
    {	
		imx6ull_led_ctl(p_chrdev, LEDON);		/* 打开LED灯 */
	} 
	else if(ledstat == LEDOFF) 
    {
		imx6ull_led_ctl(p_chrdev, LEDOFF);	/* 关闭LED灯 */
	}

#if 0
    int i = 0;
    for (i = 0; i < BUFSIZE; i++)
    {
        PRT("buf[%d] %c\n", i, p_chrdev->buf[i]); // 将mem中的值打印出来
    }
    PRT("write offset is %llu, count is %d\n", offset, count); // 打印写入的值
    #endif
    *off = *off + count;                         // 更新偏移值

    return count;
}

static int scdev_ops_release(struct inode *pInode, struct file *pFile)
{
    //_CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)pFile->private_data; //在write函数中读取private_data
    #if MUTEX_FLAG == 1
    /* 释放互斥锁 */
	mutex_unlock(&p_chrdev->mutex_lock);
    #endif
    //PRT("This is scdev_ops_release!dev_name=%s\n", p_chrdev->dev_name);
    return 0;
}

static loff_t scdev_ops_llseek(struct file *pFile, loff_t offset, int whence)
{
    loff_t new_offset = 0; // 定义loff_t类型的新的偏移值
    switch (whence)    // 对lseek函数传递的whence参数进行判断
    {
        case SEEK_SET:
            if (offset < 0 || offset > BUFSIZE)
            {
                return -EINVAL; // EINVAL=22 表示无效参数
            }
            new_offset = offset; // 如果whence参数为SEEK_SET，则新偏移值为offset
            break;
        case SEEK_CUR:
            if ((pFile->f_pos + offset < 0) || (pFile->f_pos + offset > BUFSIZE))
            {
                return -EINVAL;
            }
            new_offset = pFile->f_pos + offset; // 如果whence参数为SEEK_CUR，则新偏移值为pFile->f_pos + offset，pFile->f_pos为当前的偏移值
            break;
        case SEEK_END:
            if (pFile->f_pos + offset < 0)
            {
                return -EINVAL;
            }
            new_offset = BUFSIZE + offset; // 如果whence参数为SEEK_END，则新偏移值为BUFSIZE + offset，BUFSIZE为最大偏移量
            break;
        default:
            break;
    }
    pFile->f_pos = new_offset; // 更新pFile->f_pos偏移值

    return new_offset;
}

static long scdev_ops_ioctl(struct file *pFile, unsigned int cmd, unsigned long arg)
{
    int val = 0;//定义 int 类型向应用空间传递的变量 val
    PRT("_IOC_TYPE=0x%x _IOC_DIR=0x%x _IOC_NR=0x%x _IOC_SIZE=0x%x",
        _IOC_TYPE(cmd), _IOC_DIR(cmd), _IOC_NR(cmd), _IOC_SIZE(cmd));
    switch(cmd)
    {
        case CMD_TEST0:
            PRT("this is CMD_TEST0!\n");
            break;
        case CMD_TEST1:
            PRT("this is CMD_TEST1! arg is %ld\n",arg);//打印应用空间传递来的 arg 参数
            break;
        case CMD_TEST2:
            val = 1;//将要传递的变量 val 赋值为 1
            PRT("this is CMD_TEST2!\n");
            if(copy_to_user((int *)arg, &val, sizeof(val)) != 0)
            {
                //通过 copy_to_user 向用户空间传递数据
                PRT("copy_to_user error \n");
            }
            break;
        case CMD_TEST3:
        {
            struct __CMD_TEST cmd_test3 = {0};
            // 使用 access_ok (type, addr, size); 来检查用户空间内存块是否可用
            // 在不同的内核版本中，函数也可能没有type参数：access_ok (addr, size);
            #if (LINUX_VERSION_CODE <= KERNEL_VERSION(4, 20, 17))
            if (!access_ok(VERIFY_WRITE, arg, sizeof(struct __CMD_TEST)))
            {
                return -1;
            }
            #else
            if (!access_ok(arg, sizeof(struct __CMD_TEST)))
            {
                return -1;
            }
            #endif
            // likely 和 unlikely 宏， 会让编译器总是将大概率执行的代码放在靠前的位置， 从而提高驱动的效率。
            // likely(x) 与 unlikely(x) 的作用就是表达式 x 为真的可能性更大（likely） 和更小（unlikely） 。
            // 在传递地址正确的前提下copy_from_user 函数运行失败为小概率事件， 所以这里使用 unlikely 函数进行驱动效率的优化。
            if (unlikely(copy_from_user(&cmd_test3, (int *)arg, sizeof(cmd_test3)) != 0))
            {
                PRT("copy_from_user error\n");
            }
            PRT("cmd_test3 data: .a=%d .b=%d .c=%d\n", cmd_test3.a, cmd_test3.b, cmd_test3.c);
            break;
        }
        default:
            break;
    }

    return 0;
}

static __poll_t scdev_ops_poll(struct file *pFile, struct poll_table_struct *p)
{
    _CHAR_DEVICE *p_chrdev=(_CHAR_DEVICE *)pFile->private_data; //在write函数中读取private_data
    __poll_t mask = 0;

    poll_wait(pFile, &p_chrdev->r_wait, p); // 应用阻塞

    if (p_chrdev->condition == 1)
    {
        mask |= POLLIN;
    }

    return mask;
}

/**
 * @brief  scdev_ops_fasync()
 * @note   fasync函数，用于处理异步通知
 * @param [in] fd    文件描述符
 * @param [in] pFilp 要打开的设备文件(文件描述符)
 * @param [in] on    模式
 * @param [out]
 * @retval 负数表示函数执行失败
 */
static int scdev_ops_fasync(int fd, struct file *pFile, int on)
{
    _CHAR_DEVICE *p_chrdev = (_CHAR_DEVICE *)pFile->private_data; // 在write函数中读取private_data
    return fasync_helper(fd, pFile, on, &p_chrdev->fasync_queue);
}

static struct file_operations g_scdev_ops = {
    .owner = THIS_MODULE, // 将owner字段指向本模块，可以避免在模块的操作正在被使用时卸载该模块
    .open = scdev_ops_open,
    .read = scdev_ops_read,
    .write = scdev_ops_write,
    .release = scdev_ops_release,
    .llseek = scdev_ops_llseek,
    .unlocked_ioctl = scdev_ops_ioctl,
    .poll = scdev_ops_poll,     // 将 poll 字段指向 chrdev_poll(...)函数
    .fasync = scdev_ops_fasync, // 将fasync字段指向 scdev_ops_fasync(...)函数
}; // 定义file_operations结构体类型的变量g_cdev_dev_ops

static int scdev_create(_CHAR_DEVICE *p_chrdev)
{
    int ret;          // 定义int类型的变量ret，用来判断函数返回值
    int major, minor; // 定义int类型的主设备号major和次设备号minor
    #if MUTEX_FLAG == 1
	mutex_init(&p_chrdev->mutex_lock);	/* 初始化互斥锁 */
    #endif
    ret = alloc_chrdev_region(&p_chrdev->dev_num, 0, 1, CHRDEV_NAME); // 自动获取设备号，设备名为chrdev_name
    if (ret < 0)
    {
        PRTE("alloc_chrdev_region is error!ret=%d\n", ret);
        goto err_alloc_devno;
    }
    major = MAJOR(p_chrdev->dev_num); // 使用MAJOR()函数获取主设备号
    minor = MINOR(p_chrdev->dev_num); // 使用MINOR()函数获取次设备号
    //PRT("major is %d, minor is %d !\n", major, minor);

    cdev_init(&p_chrdev->s_cdev, &g_scdev_ops); // 使用cdev_init()函数初始化p_chrdev->s_cdev结构体，并链接到 cdev_ops 结构体
    p_chrdev->s_cdev.owner = THIS_MODULE;          // 将owner字段指向本模块，可以避免在模块的操作正在被使用时卸载该模块
    ret = cdev_add(&p_chrdev->s_cdev, p_chrdev->dev_num, 1); // 使用cdev_add()函数进行字符设备的添加
    if (ret < 0)
    {
        PRTE("cdev_add is error !ret=%d\n", ret);
        goto err_cdev_add;
    }

    p_chrdev->class = class_create(THIS_MODULE, CLASS_NAME); // 使用class_create进行类的创建，类名称为 class_dev
    if(IS_ERR(p_chrdev->class))
    {
        ret = PTR_ERR(p_chrdev->class);
        goto err_class_create;
    }
    p_chrdev->device = device_create(p_chrdev->class, NULL, p_chrdev->dev_num, NULL, "%s", DEVICE_NAME); // 使用device_create进行设备的创建，设备名称为 device_dev
    if(IS_ERR(p_chrdev->device))
    {
        ret = PTR_ERR(p_chrdev->class);
        goto err_device_create;
    }
    snprintf (p_chrdev->dev_name, sizeof(p_chrdev->dev_name), "/dev/%s", DEVICE_NAME);

    /* 初始化等待队列头 */
	init_waitqueue_head(&p_chrdev->r_wait);

    //PRT("scdev_create %s success!\n", p_chrdev->dev_name);
    return 0;
    // 一些列的错误处理
err_device_create:
    class_destroy(p_chrdev->class);   // 删除创建的类
err_class_create:
    cdev_del(&p_chrdev->s_cdev);      // 使用cdev_del()函数进行字符设备的删除
err_cdev_add:
    unregister_chrdev_region(p_chrdev->dev_num, 1); //注销设备号
err_alloc_devno:
    return ret;
}

static void scdev_destroy(_CHAR_DEVICE *p_chrdev)
{
    // 需要注意的是， 字符设备的注册要放在申请字符设备号之后，
    // 字符设备的删除要放在释放字符驱动设备号之前。
    cdev_del(&p_chrdev->s_cdev);                    // 使用cdev_del()函数进行字符设备的删除
    unregister_chrdev_region(p_chrdev->dev_num, 1); // 释放字符驱动设备号
    device_destroy(p_chrdev->class, p_chrdev->dev_num); // 删除创建的设备
    class_destroy(p_chrdev->class);                 // 删除创建的类
    //PRT("scdev_destroy success!\n");
}


/**
 * @brief  sdrv_probe()
 * @note   平台设备的探测函数
 * @param [in]
 * @param [out]
 * @retval 
 */
static int sdrv_probe(struct platform_device *pdev)
{
    int ret = 0;

    PRT("probing platform device & driver!pdev->name=%s\n", pdev->name);

    // 注册字符设备
    ret = scdev_create(&g_chrdev);
    if (ret < 0) 
    {
        PRTE("Failed to scdev_create!ret=%d\n", ret);
        goto err_scdev_create;
    }

    // 1. 寄存器映射
    g_chrdev.nd = pdev->dev.of_node;
    PRT("platform_device.dev.of_node.name=%s\n", pdev->dev.of_node->name);
    ret = imx6ull_led_register_map(&g_chrdev);
    if(ret < 0)
    {
        PRTE("imx6ull_led_register_map fail!ret=%d\n", ret);
    }
    // 2. 初始化LED
    imx6ull_led_init(&g_chrdev);

    return 0;
err_scdev_create:
    return ret;
}

/**
 * @brief  sdrv_remove()
 * @note   平台设备的移除函数
 * @param [in]
 * @param [out]
 * @retval 
 */
static int sdrv_remove(struct platform_device *pdev)
{
    PRT("removing platform driver!pdev->name=%s\n", pdev->name);

    imx6ull_led_uinit(&g_chrdev);//取消led相关寄存器映射
    // 注销字符设备
    scdev_destroy(&g_chrdev);
    
    return 0;
}

/* 设备树匹配列表 */
static const struct of_device_id led_of_match[] = {
	{ .compatible = "led" }, // 这里用于匹配设备树中的 compatible 属性节点
	{ /* Sentinel */ }
};

/**
 * 定义平台驱动结构体
 */
static struct platform_driver g_sdrv_platform = {
    .probe = sdrv_probe,   // 平台设备的探测函数指针
    .remove = sdrv_remove, //  平台设备的移除函数指针
    .driver = {
        .name = "imx6ul-led-driver", // 和设备名称相同时，可以匹配成功
                                   // 会在 /sys/bus/platform/drivers中创建对应目录，即/sys/bus/platform/drivers/driver-name
        .owner = THIS_MODULE,
		.of_match_table	= led_of_match, /* 设备树匹配表 */
    },
};

/**
 * @brief  sdrv_demo_init
 * @note   调用driver_register函数注册我们的驱动
 * @param [in]
 * @param [out]
 * @retval 
 */
static __init int sdrv_demo_init(void)
{
    int ret = 0;
	printk("*** [%s:%d]Build Time: %s %s, git version:%s LINUX_VERSION=%d.%d.%d ***\n", __FUNCTION__,
           __LINE__, KERNEL_KO_DATE, KERNEL_KO_TIME, KERNEL_KO_VERSION, 
           (LINUX_VERSION_CODE >> 16) & 0xff, (LINUX_VERSION_CODE >> 8) & 0xff, LINUX_VERSION_CODE & 0xff);

	// 注册平台驱动
    ret = platform_driver_register(&g_sdrv_platform);
    if (ret) 
    {
        PRT("Failed to register platform driver!ret=%d\n", ret);
        goto err_platform_driver_register;
    }

    //PRT("sdrv_demo module init success!\n");
	return 0;

err_platform_driver_register:
    return ret;
}

/**
 * @brief  sdrv_demo_exit
 * @note   注销驱动
 * @param [in]
 * @param [out]
 * @retval 
 */
static __exit void sdrv_demo_exit(void)
{
    // 注销平台驱动
    platform_driver_unregister(&g_sdrv_platform);
    //PRT("sdrv_demo module exit!\n");
}

module_init(sdrv_demo_init); // 将__init定义的函数指定为驱动的入口函数
module_exit(sdrv_demo_exit); // 将__exit定义的函数指定为驱动的出口函数

/* 模块信息(通过 modinfo xxx.ko 查看) */
MODULE_LICENSE("GPL v2");            /* 源码的许可证协议 */
MODULE_AUTHOR("sumu");               /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description");   /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */
